/**  
  -- The Performance Driven Lime Project -- 

  A self adaptive tuple space for mobile agents and hosts by dynamic
  adapting tuple's replication policy.
  
  Copyright (C) 2008
  Xing Jiankuan, Pan Wenhui
  SAGroup of Tsinghua University

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */

package sagroup.pdlime;

import java.util.Enumeration;
import java.util.Hashtable;
import sagroup.pdlime.TVRLimeTemplate;

import lights.adapters.Field;
import lights.adapters.Tuple;
import lights.interfaces.ITuple;

import lime2.BootException;
import lime2.InvalidLocationException;
import lime2.LimeServer;
import lime2.LimeServerID;
import lime2.LimeSystemTupleSpace;
import lime2.LimeTuple;
import lime2.LimeTupleSpace;
import lime2.Location;
import lime2.NamingException;
import lime2.Policies;
import lime2.PolicyException;
import lime2.ReactionID;
import lime2.Translator;
import lime2.TupleID;

/**
 * <p>
 * The basic tuple space that support "Type", "Version" and "Replication". This
 * class use LimeTupleSpace as the basic facility and performs common
 * operations. However the replication and their update mechanism are not
 * implemented here. User should inherit this class to finish the replication
 * methods, such as how to register/deregister a replication relationship among
 * nodes, how to push the update and fine-tune the replication policies.
 * </p>
 * <p>
 * This tuple space uses TVRLimeTuple and TVRLimeTemplate as the interface. So,
 * type, version are completely supported. Replication is partial cared here.
 * Therefore, you can use this class directly if you do not care replication
 * issues.
 * </p>
 * 
 * @author Pan Wenhui
 * @author Xing Jiankuan
 */
public class TVRLimeTupleSpace {
	/**
	 * this hashtable[String,TVRLimeTupleSpace] keeps track of the existing
	 * TVRLimeTupleSpaces for this host
	 */
	protected static Hashtable TVRLimeTuplespaces = new Hashtable();

	/**
	 * the host where this tuple space locates
	 */
	protected static LimeServerID localLimeServerID = LimeServer.getServer()
			.getServerID();

	/**
	 * this (long) global counter is used to enforce uniqueness of the numeric
	 * identifiers for the Tuple's version layer IDs
	 */
	private static long NEXT_VID_NUM = -1;

	/**
	 * the LimeTupleSpace of the VersionedLimeTupleSpace
	 */
	protected LimeTupleSpace lts;

	/**
	 * Create a TVRLimeTupleSpace by given name and policy
	 * 
	 * @param name
	 *            tuple space's name
	 * @param policy
	 *            see {@link lime2.Policies}
	 * @throws PolicyException
	 *             when policy is unknown, this exception is thrown
	 * @see {@link LimeTupleSpace#get(String, byte)}
	 */
	protected TVRLimeTupleSpace(String name, byte policy)
			throws PolicyException {
		super();

		// creates the LimeTupleSpace
		this.lts = LimeTupleSpace.get(name, policy);
	}

	/**
	 * Creates a new TVRLimeTupleSpace with a given name, if no
	 * TVRLimeTupleSpace with that name already exists. Otherwise returns the
	 * existing TVRLimeTupleSpace, thus <u>forcing agents to share it</u>. The
	 * policy is set to default. <br>
	 * For more details, see {@link LimeTupleSpace#get(String)}
	 * 
	 * @param name
	 *            of the TVRLimeTupleSpace we want to use
	 * @return TVRLimeTupleSpace reference
	 * @throws BootException
	 * @throws NamingException
	 */
	synchronized public static TVRLimeTupleSpace get(String name)
			throws PolicyException {
		TVRLimeTupleSpace ret = null;
		if (name != null) {
			ret = (TVRLimeTupleSpace) TVRLimeTuplespaces.get(name);
			if (ret == null) {
				ret = new TVRLimeTupleSpace(name, Policies.DEFAULT);
				TVRLimeTuplespaces.put(name, ret);
			}
		}
		return ret;
	}

	/**
	 * Same as the previous "get" method; but this one allows to set policy of
	 * the LimeTupleSpace at creation time. Please note that if the
	 * LimeTupleSpace already exists with a different policy, a PolicyException
	 * is thrown. Lime doesn't force consensus on a policy for the tuple spaces
	 * of a federation: it is the application programmer (.. 's agent/s) that
	 * must select the same policy on every host (.. and this makes sense).
	 * 
	 * @param name
	 *            of the LimeTupleSpace we want to use
	 * @param pol
	 *            policy we want to apply to the LimeTupleSpace
	 * @return LimeTupleSpace
	 * @throws BootException
	 * @throws NamingException
	 * @throws PolicyException
	 */
	synchronized public static TVRLimeTupleSpace get(String name, byte pol)
			throws PolicyException {

		TVRLimeTupleSpace ret = null;
		if (name != null) {
			ret = (TVRLimeTupleSpace) TVRLimeTuplespaces.get(name);
			if (ret == null) {
				ret = new TVRLimeTupleSpace(name, pol);
				TVRLimeTuplespaces.put(name, ret);
			} else if (pol != ret.lts.getPolicy())
				throw new PolicyException("policy has already been set to "
						+ Translator.translatePolicy(ret.lts.getPolicy()));
		}
		return ret;
	}

	/**
	 * Returns the policy for the TVRLimeTupleSpace
	 * 
	 * @return policy
	 */
	public byte getLimePolicy() {
		return lts.getPolicy();
	}

	/**
	 * Returns the next TupleID for this host. <br>
	 * The numeric identifier of the first TupleID created on this host is zero.
	 * Synchronization is necessary since multiple agents (threads) could try to
	 * access the global counter ("tids") when we are in the middle of a
	 * previous invocation of this method (.. this would ruin uniqueness of
	 * tuple identifiers).
	 * 
	 * @return a valid version layered TupleID
	 */
	synchronized static TupleID nextVID() {
		NEXT_VID_NUM++;
		return new TupleID(Location.HERE.getID(), NEXT_VID_NUM);
	}

	/**
	 * Returns the name of this TVRLimeTupleSpace.
	 * 
	 * @return name
	 */
	public String getName() {
		return lts.getName();
	}

	/**
	 * Returns the names of all the TVRLimeTupleSpaces for this host.
	 * 
	 * @return array of names
	 */
	public static String[] getNames() {
		String[] ret = new String[TVRLimeTuplespaces.size()];
		int i = 0;
		for (Enumeration e = TVRLimeTuplespaces.keys(); e.hasMoreElements();)
			ret[i++] = (String) e.nextElement();
		return ret;
	}

	/**
	 * Reads a tuple matching a specific TVRLimeTemplate.
	 * 
	 * @param t
	 *            TVRLimeTemplate
	 * @return result LimeTuple
	 */
	public TVRLimeTuple rd(TVRLimeTemplate vlt) {
		// parameter checking
		if (vlt == null)
			throw new IllegalArgumentException("null value");

		LimeTuple lt = lts.rd(vlt.toLimeTemplate());
		return new TVRLimeTuple(lt);
	}

	/**
	 * Non-blocking read of a tuple matching a specific TVRLimeTemplate.
	 * 
	 * @param t
	 *            TVRLimeTemplate
	 * @return result TVRLimeTuple
	 */
	public TVRLimeTuple rdp(TVRLimeTemplate vlt) {
		// parameter checking
		if (vlt == null)
			throw new IllegalArgumentException("null value");

		LimeTuple lt = lts.rdp(vlt.toLimeTemplate());
		return new TVRLimeTuple(lt);
	}

	/**
	 * Takes a tuple matching a specific TVRLimeTemplate.
	 * 
	 * @param t
	 *            TVRLimeTemplate
	 * @return result LimeTuple
	 */
	public TVRLimeTuple in(TVRLimeTemplate vlt) {
		// parameter checking
		if (vlt == null)
			throw new IllegalArgumentException("null value");

		LimeTuple lt = lts.in(vlt.toLimeTemplate());
		return new TVRLimeTuple(lt);
	}

	/**
	 * Non-blocking take of a tuple matching a specific TVRLimeTemplate.
	 * 
	 * @param t
	 *            TVRLimeTemplate
	 * @return result TVRLimeTuple
	 */
	public TVRLimeTuple inp(TVRLimeTemplate vlt) {
		// parameter checking
		if (vlt == null)
			throw new IllegalArgumentException("null value");

		LimeTuple lt = lts.inp(vlt.toLimeTemplate());
		return new TVRLimeTuple(lt);
	}

	/**
	 * Change a tuple, that is, update its content and version.
	 * 
	 * @Requires: nothing.
	 * @Modifies: nothing.
	 * @Effects: If the destination location is not UNSPECIFIED, any parameter
	 *           is not null, the template is not for replica, out the tuple.<br>
	 *           Else throw exception. User should not modify the parameter t!
	 * 
	 * @param vlt
	 * @param t
	 *            user data tuple
	 * @return the TVRLimeTuple after change, if change is successful, otherwise
	 *         return null
	 */
	public TVRLimeTuple change(TVRLimeTemplate vlt, ITuple t) {
		// parameter checking
		if (vlt == null || t == null)
			throw new IllegalArgumentException("null value");
		if (!vlt.isMaster())
			throw new IllegalArgumentException("must not be replica");

		TVRLimeTuple toReturn = null;
		// TODO: there maybe a better synchronization implementation.
		synchronized (lts) {
			LimeTuple lt = lts.inp(vlt.toLimeTemplate());
			if (lt != null) {
				TVRLimeTuple tlt = new TVRLimeTuple(lt);
				// update information
				tlt.ver = new Long(tlt.ver.longValue() + 1L);
				tlt.setTuple(t);
				// write back
				LimeTuple result = lts.out(tlt.toFlatTuple());
				toReturn = new TVRLimeTuple(result);
			}
		}
		return toReturn;
	}

	// TODO can TVRLimeTuple be parameter from user?
	public TVRLimeTuple out(TVRLimeTuple tlt) {
		return out(tlt.getType(), tlt.getDest(), tlt.getTuple());
	}

	/**
	 * Out a tuple locally
	 * 
	 * @param t
	 *            user data tuple
	 * @return out's result TVRLimeTuple
	 */
	public TVRLimeTuple out(TupleType type, ITuple t) {
		return out(type, Location.HERE, t);
	}

	/**
	 * Out a tuple to a specific destination location. <br>
	 * 
	 * @Requires: nothing.
	 * @Modifies: nothing.
	 * @Effects: If the destination location is not UNSPECIFIED and any
	 *           parameter is not null, out the tuple.<br>
	 *           Else throw exception.
	 * 
	 * @param t
	 *            ITuple (not limified or versionfied, just a normal ITuple)
	 * @param whereto
	 *            destination Location
	 * @return result TVRLimeTuple
	 * @throws InvalidLocationException
	 */
	public TVRLimeTuple out(TupleType type, Location whereto, ITuple t) {
		// parameter checking
		if (type == null || whereto == null || t == null)
			throw new IllegalArgumentException("null value");
		if (whereto.isUnspecified())
			throw new InvalidLocationException(
					"destination cannot be unspecified");
		TVRLimeTuple tlt = TVRLimeTuple.createMaster(type, whereto, t);
		LimeTuple result = lts.out(tlt.toFlatTuple());
		return new TVRLimeTuple(result);
	}

	/**
	 * Out a tuple to multiple nodes, just as multicast. We make it public
	 * because it may be useful for application developer. <br>
	 * In implementation, we call <code>out()</code> iterative for every
	 * LimeServer. For other information, refer to <code>out</code>.<br>
	 * 
	 * @Requires: nothing.
	 * @Modifies: nothing.
	 * @Effects: If any parameter is not null, out the tuple.<br>
	 *           Else throw exception.
	 * @param tupleType
	 *            tuple's type
	 * @param wheretos
	 *            the array of target lime server IDs
	 * @param t
	 *            the user data tuple to be sent
	 * 
	 * @return an array of TVRLimeTuple instances that are sent to all
	 *         "wheretos". One host, one result element in the array.
	 */
	public TVRLimeTuple[] outToMulti(TupleType tupleType,
			LimeServerID[] wheretos, ITuple t) {
		// parameter checking
		if (tupleType == null || t == null || Util.testNullArray(wheretos))
			throw new IllegalArgumentException("null value");

		TVRLimeTuple[] toReturn = new TVRLimeTuple[wheretos.length];
		for (int i = 0; i < wheretos.length; i++) {
			toReturn[i] = out(tupleType, new Location(wheretos[i]), t);
		}
		return toReturn;
	}

	/**
	 * Out tuple to all other engaged hosts.
	 * 
	 * @param tupleType
	 *            tuple's type
	 * @param t
	 *            the tuple to be sent
	 * @return an array of TVRLimeTuple instances that are sent to all engaged
	 *         hosts. One host, one result element in the array.
	 */
	public TVRLimeTuple[] outBroad(TupleType tupleType, ITuple t) {
		// parameter checking
		if (tupleType == null || t == null)
			throw new IllegalArgumentException("null value");

		LimeServerID[] wheretos = getAllEngagedHosts();
		return outToMulti(tupleType, wheretos, t);
	}

	/**
	 * Get all engaged hosts' lime server IDs (except self).
	 * 
	 * @Request: nothing.
	 * @Modifies: nothing.
	 * @Effects: If no Host, then return zero length array. else return array of
	 *           engaged LimeServers' IDs.
	 * @return an array of hosts' lime server IDs.
	 */
	protected LimeServerID[] getAllEngagedHosts() {
		LimeTuple[] ts = LimeSystemTupleSpace.get().rdg(
				new Tuple()
						.add(new Field().setValue(LimeSystemTupleSpace.HOST)));
		if (ts == null) {
			return new LimeServerID[0];
		}
		LimeServerID[] toReturn = new LimeServerID[ts.length];
		for (int i = 0; i < ts.length; i++) {
			toReturn[i] = (LimeServerID) ts[i].getTuple().get(1);
		}
		return toReturn;
	}

	/**
	 * Add a series of strong reactions
	 * 
	 * @param versionedReactions
	 *            array of reactions to be added
	 * @return the added reactions' IDs
	 * @author Xing Jiankuan
	 */
	public ReactionID[] addStrongReaction(VersionedReaction[] versionedReactions) {
		if (Util.testNullArray(versionedReactions))
			throw new IllegalArgumentException("null value");
		return lts.addStrongReaction(VersionedReaction
				.getReactions(versionedReactions));
	}

	/**
	 * Removes strong reactions from this host. <br>
	 * If the ReactionIDs objects do not correspond to strong reactions
	 * installed previously on this host, the reactions will not be
	 * deregistered. You can uninstall a strong reaction from a remote host
	 * using an agent (.. you still need the ReactionID "handle").
	 * 
	 * @param ids
	 *            of the (registered) reactions to remove
	 * @return boolean[], where <code>boolean[i]</code> is <code>true</code>
	 *         if reaction <code>i</code> was successfully removed,
	 *         <code>false</code> otherwise
	 */
	public boolean[] removeStrongReaction(ReactionID[] ids) {
		return lts.removeStrongReaction(ids);
	}

	/**
	 * Add a series of weak reactions
	 * 
	 * @param versionedReactions
	 *            array of reactions to be added
	 * @return the added reactions' IDs
	 * @author Xing Jiankuan
	 */
	public ReactionID[] addWeakReaction(VersionedReaction[] versionedReactions) {
		if (Util.testNullArray(versionedReactions))
			throw new IllegalArgumentException("null value");
		return lts.addWeakReaction(VersionedReaction
				.getReactions(versionedReactions));
	}

	public boolean[] removeWeakReaction(ReactionID[] ids) {
		boolean[] ret = lts.removeWeakReaction(ids);
		for (int i = 0; i < ret.length; i++) {
			if (!ret[i]) {
				//TODO: matbe should use logging.
				System.out.println("fail remove weak reaction with id: "
						+ ids[i]);
			}
		}
		return ret;
	}
}
